/**
 * \file mutex.cc
 * \brief System mutexes
 * \author Corey Tabaka
 */

/*
   Copyright 2005 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include <mutex.h>
#include <atomic.h>
#include <Thread.h>

/*
 * Initialize a dynamically allocated mutex_t
 */
void mutex_init(mutex_t *mutex) {
	interrupt_mutex_init(&mutex->lock);
	
	mutex->count = 0;
	mutex->threads.initialize();
}

/*
 * Attempt to lock the mutex, entering wait state on failure.
 *
 * An interesting point to note is that a thread will not be deallocated while
 * waiting for a lock because a reference will be maintained for the duration of
 * the wait.
 */
void mutex_wait(mutex_t *mutex) {
	if (atomic_exchange(1, &mutex->count) != 0) {
		RefThread thread = Thread::getCurrentThread();
		
		// add this thread to end of the list of waiting tasks
		interrupt_mutex_wait(&mutex->lock);
		mutex->threads.append(thread);
		interrupt_mutex_signal(&mutex->lock);
		
		do {
			// block until notify
			thread->wait();
			// try to obtain the lock
		} while (atomic_exchange(1, &mutex->count) != 0);
		
		interrupt_mutex_wait(&mutex->lock);
		mutex->threads.remove(thread);
		interrupt_mutex_signal(&mutex->lock);
	}
}

/*
 * Attempt to lock the mutex, returning status. This function does not block
 * on an already locked mutex.
 *
 * Returns 0 (previously unlocked) on success, nonzero on failure.
 */
int mutex_try(mutex_t *mutex) {
	return atomic_exchange(1, &mutex->count);
}

/*
 * Release a locked mutex. Wake the head of the wait queue and free resources.
 */
void mutex_signal(mutex_t *mutex) {
	interrupt_mutex_wait(&mutex->lock);
	Thread *thread = (Thread *) mutex->threads.getHead();
	interrupt_mutex_signal(&mutex->lock);
	
	// unlock mutex
	atomic_exchange(0, &mutex->count);
	
	if (thread) {
		thread->notify();
	}
}

void semaphore_init(semaphore_t *sem, int count) {
	interrupt_mutex_init(&sem->lock);
	sem->count = count;
	sem->threads.initialize();
}

void semaphore_wait(semaphore_t *sem) {
	RefThread thread = Thread::getCurrentThread();
	
	interrupt_mutex_wait(&sem->lock);
	sem->threads.append(thread);
	interrupt_mutex_signal(&sem->lock);
	
	if (atomic_add(-1, &sem->count) <= 0) {
		thread->wait();
	}
	
	interrupt_mutex_wait(&sem->lock);
	sem->threads.remove(thread);
	interrupt_mutex_signal(&sem->lock);
}

void semaphore_signal(semaphore_t *sem) {
	Thread *thread = 0;
	
	if (atomic_add(1, &sem->count) < 0) {
		// see if another thread is waiting on this semaphore
		interrupt_mutex_wait(&sem->lock);
		thread = (Thread *) sem->threads.getHead();
		interrupt_mutex_signal(&sem->lock);
	}
		
	if (thread) {
		thread->notify();
	}
}
